/*
 * sqlhandle.cpp
 *
 *  Created on: Dec 9, 2010
 *      Author: khanton
 */

#include <iostream>
#include <query.h>
#include <connection.h>
#include <boost/format.hpp>

#include "sqlhandle.h"
#include "log.h"
#include "controllers_network.h"
#include "options.h"
#include "con_state.h"

sqlhandle::sqlhandle()
{

}

sqlhandle::~sqlhandle()
{
	close();
	delete pool_;
}

void sqlhandle::start(const std::string &_host, const std::string &_user, const std::string &_password, const std::string &_database)
{
	pool_ = new asud_connection_pool(_host, _user, _password, _database);
}

void sqlhandle::close()
{
}

bool sqlhandle::create_controller_on_network(const con_state::controller_id _controller, std::string &_key)
{
	mysqlpp::Connection *connection = NULL;
	
	bool ret = true;

	try
	{
		connection = pool_->safe_grab();
		
		mysqlpp::Transaction trans(*connection);
		
		mysqlpp::Query query(connection);
		
		query << "select c_id, enc_key from network_map where c_id=" << _controller;

		dump_query(query.str());
			
		if (mysqlpp::UseQueryResult res = query.use())
		{
			if (mysqlpp::Row row = res.fetch_row())
			{
			  _key = std::string(row["enc_key"]);
			}  
			else
			{
			  throw std::logic_error("No controler");
			}
		}
		else
		{
		  throw std::logic_error("No controler");
		}

	}
	catch(const std::exception &_error)
	{
		log(log_event::controller(_controller, log_event::et_critical, "Попытка подключения несуществующего контроллера."));

	    g_log.msg(MSG_NORMAL, str(boost::format("Can not load controller network: %s") % _error.what()));

	    ret = false;
	}

	if (connection)
		pool_->release(connection);
		
	return ret;		
}

void sqlhandle::delete_controller_from_network(const con_state::controller_id _controller)
{

	mysqlpp::Connection *connection = NULL;
	
	try
	{
		connection = pool_->safe_grab();

		mysqlpp::Query query(connection);

		query << "update network_map set online=0 where c_id=" << _controller;

		dump_query(query.str());

		query.execute();

		log(log_event::controller(_controller, log_event::et_normal, "Нет связи.", ""));

	}
	catch(const std::exception &_error)
	{
	    g_log.msg(MSG_NORMAL, str(boost::format("Can not load controller network: %s") %_error.what()));

		log(log_event::controller(_controller, log_event::et_critical, "Попытка отключения несуществующего контроллера."));
	}

	if (connection)
	{
		pool_->release(connection);
	}
}

void sqlhandle::dump_query(const std::string &_query) const
{
	if (g_config.m_dump_all_query)
	{
	  g_log.msg(MSG_NORMAL, boost::format("SQL QUERY:%s") %_query);
	}
}


void sqlhandle::set_controller_state(con_state::controller_id _id, const con_state &_new_state, const con_state &_old_state)
{
	mysqlpp::Connection *connection = NULL;
	
	try
	{
		connection = pool_->safe_grab();

		tm ctime = boost::posix_time::to_tm(_new_state.as_local_time()); // here we may can get bad date exception
																	
		time_t cont_time = mktime(&ctime);
		time_t chg_time;
		time(&chg_time);
		
		mysqlpp::Transaction trans(*connection);

		mysqlpp::Query query(connection);

		query << "update network_map set" <<
				" flash_status=" << static_cast<int>(_new_state.flash_status_) <<
				",job_mode=" << static_cast<int>((_new_state.power_alarm_ != 0) ?
						con_state::jm_PWR_FAILURE :  _new_state.job_mode_) <<
				",prog=" << static_cast<int>(_new_state.current_program_) <<
				",phase=" << static_cast<int>(_new_state.current_phase_) <<
				",fault=" << static_cast<int>(_new_state.fault_type_) <<
				",fault_red=" << static_cast<int>(_new_state.fault_red_) <<
				",fault_yellow=" << static_cast<int>(_new_state.fault_yellow_) <<
                ",fault_green=" << static_cast<int>(_new_state.fault_green_) <<
                ",no_program=" << static_cast<int>(_new_state.no_program_) <<
                ",cont_local_time=FROM_UNIXTIME(" << cont_time << ")" <<
                ",cont_scan_time=FROM_UNIXTIME(" << chg_time << ")" <<
                	((_old_state.job_mode_ == con_state::jm_NOT_CONNECT) ?
                			str(boost::format(",connect_time=FROM_UNIXTIME(%1%)") % chg_time )
                			: "") <<
                ",online=1" <<
                " where c_id=" << _id;

		dump_query(query.str());

		query.execute();
		
		trans.commit();
		
		log_event::log_event_list_t events;


		if (_old_state.job_mode_ == con_state::jm_NOT_CONNECT)
		{
			events.push_back(log_event::controller(_id, log_event::et_normal, "Восстановление связи."));

		}

		if ((_new_state.power_alarm_ != 0) && (_old_state.power_alarm_ == 0))
		{
			events.push_back(log_event::controller(_id, log_event::et_critical, "На СО отключено питание.", (6 << 8)));
		}

		if ((_new_state.power_alarm_ == 0) && (_old_state.power_alarm_ != 0))
		{
			events.push_back(log_event::controller(_id, log_event::et_normal, "Питание на СО восстановлено."));
		}

		if (_old_state.job_mode_ != _new_state.job_mode_)
		{
			events.push_back(log_event::controller(_id,log_event::et_state_change,
					str(boost::format("Новый режим работы: %s") % con_state::job_mode_desc(_new_state.job_mode_)),""
					));
		}

		if (_old_state.current_program_ != _new_state.current_program_)
		{
			if (_new_state.current_program_ == 9)
			{
			  events.push_back(log_event::controller(_id,log_event::et_state_change, "Контроллер в режиме: ЖМ"));
			}
			else if (_new_state.current_program_ == 10)
			{
			  events.push_back(log_event::controller(_id,log_event::et_state_change, "Светофорный обьект отключен"));
			}
			else if (_new_state.current_program_ == 11)
			{
			   // Hmm secret ethoteric programm 
			}
			else
			{
			  events.push_back(log_event::controller(_id,log_event::et_state_change,
					str(boost::format("Изменилась [программа]. Новая программа : %d")
			  	  	  	  	  	  	  % static_cast<unsigned int>(_new_state.current_program_))));
		   }					
		}

		if ((_old_state.current_phase_ != _new_state.current_phase_) && (_new_state.current_phase_ != 0))
		{
			events.push_back(log_event::controller(_id,log_event::et_state_change,
					str(boost::format("Изменилась [фаза]. Новая фаза : %d")
									% static_cast<unsigned int>(_new_state.current_phase_))));
		}

		if (_old_state.no_program_ != _new_state.no_program_)
		{
			events.push_back(log_event::controller(_id,log_event::et_state_change,
					str(boost::format("Новое состояние программы : %d")
									% static_cast<unsigned int>(_new_state.no_program_))));
		}
		
		if (_old_state.fault_type_ != _new_state.fault_type_)
		{
		  unsigned int fault = (_new_state.fault_type_ << 8);

		
		  std::string problem;
		  log_event::log_importance_t imp = log_event::et_critical;
		
		  if (_new_state.fault_type_)
		  {
			problem += "Неисправность:" ;
		  }
		  else
		  {
			imp = log_event::et_state_change;
			problem += "Неисправность устранена." ;
		  }

		  switch(_new_state.fault_type_)
		  {
			case 1:
			  problem += "Короткое замыкание.";
			break;
			
			case 2:
			  problem += "Перегрузка ";
			break;
			
			case 3:
			  problem += "Недогрузка ";
			break;
			
			case 4:
			  problem += "Перегорел красный.";
			break;
			
			case 5:
			  problem += "Конфликт зеленых.";
			break;
		  }
		  
		  if (_new_state.fault_red_ > 0)
		  {
			problem += str(boost::format(" Направление красных: %d") % static_cast<unsigned int>(_new_state.fault_red_));
			fault |= _new_state.fault_red_;
		  }

		  if (_new_state.fault_yellow_ > 0)
		  {
			problem += str(boost::format(" Направление желтых: %d") % static_cast<unsigned int>(_new_state.fault_yellow_));
			fault |= _new_state.fault_yellow_;
		  }
		  
		  if (_new_state.fault_green_ > 0)
		  {
			problem += str(boost::format(" Направление зеленых: %d") % static_cast<unsigned int>(_new_state.fault_green_));
			fault |= _new_state.fault_green_;
		  }
		  
		  events.push_back(log_event::controller(_id, imp, problem, fault));
		} 
		
		if(!events.empty())
		  log(events);
		
	}
	catch(const std::exception &_error)
	{
	    g_log.msg(MSG_NORMAL, boost::format("[%d] set_controller_state: status='Can not update controller error: %s'")
	    										% _id % _error.what());
	}

	if (connection)
	{
		pool_->release(connection);
	}
}

bool sqlhandle::get_queue_cmd(int &_command, con_state::controller_id &_id, int &_param, std::string &_user)
{
	mysqlpp::Connection *connection = NULL;

	bool ret = true;

	try
	{
		connection = pool_->safe_grab();

		mysqlpp::Transaction trans(*connection);

		mysqlpp::Query query(connection);

		query << "select uid, c_id, cmd, param, user from cmd_queue limit 1";

		std::string uid;

		dump_query(query.str());
		
		if (mysqlpp::UseQueryResult res = query.use())
		{
			if (mysqlpp::Row row = res.fetch_row())
			{
			  uid = static_cast<std::string>(row["uid"]);
			  _command = static_cast<int>(row["cmd"]);
			  _id = static_cast<int>(row["c_id"]);
			  _user = static_cast<std::string>(row["user"]);

			  try
			  {
				_param = static_cast<int>(row["param"]);
			  }
			  catch (const std::exception &_error)
			  {
				_param = 0;
			  }
			}
			else
			{
			  throw std::logic_error("No data");
			}
		}
		else
		{
		  throw std::logic_error("No data");
		}

		if (uid.empty())
		{
		  ret = false;			// cleanup all empty uid rows
		}

		query << "delete from cmd_queue where uid=\'" << uid << "\'" ;

		dump_query(query.str());

		query.execute();

		trans.commit();

	}
	catch(const std::logic_error &_error)
	{
	  ret = false;
	}
	catch(const std::exception &_error)
	{
	  g_log.msg(MSG_NORMAL, str(boost::format("Get command from queue error: %s") % _error.what()));
	  ret = false;
	}

	if (connection)
		pool_->release(connection);

	return ret;
}

bool sqlhandle::get_controller_program(const con_state::controller_id _id, std::string &_program)
{
	mysqlpp::Connection *connection = NULL;

	bool ret = true;

	try
	{
		connection = pool_->safe_grab();
		
		mysqlpp::Transaction trans(*connection);

		mysqlpp::Query query(connection);

		query << "select program from network_map where c_id=" << _id;

		std::string uid;

		dump_query(query.str());

		if (mysqlpp::UseQueryResult res = query.use())
		{
			if (mysqlpp::Row row = res.fetch_row())
			{
			  _program = std::string(row["program"]);
			}
			else
			{
			  throw std::logic_error("No data");
			}
		}
		else
		{
		  throw std::logic_error("No data");
		}

		if (_program.empty())
		{
			return true;
		}
	}
	catch(const std::logic_error &_error)
	{
	  ret = false;
	}
	catch(const std::exception &_error)
	{
		g_log.msg(MSG_NORMAL, str(boost::format("Get program error: %s") % _error.what()));
	    ret = false;
	}

	if (connection)
		pool_->release(connection);

	return ret;

}

bool sqlhandle::start_program_update(const con_state::controller_id _id)
{
	mysqlpp::Connection *connection = NULL;

	bool ret = true;

	try
	{
		connection = pool_->safe_grab();
		
		mysqlpp::Transaction trans(*connection);

		mysqlpp::Query query(connection);
		
		query << "select prog_update_status from network_map where c_id=" << _id;

		std::string uid;

		dump_query(query.str());
		
		if (mysqlpp::UseQueryResult res = query.use())
		{
			if (mysqlpp::Row row = res.fetch_row())
			{
			  int prog_status = int(row["prog_update_status"]);
			  
			  if (prog_status == 1)
			  {
				throw std::logic_error("Invalid status");
			  }
			}
			else
			{
			  throw std::logic_error("No data");
			}
		}
		else
		{
		  throw std::logic_error("No data");
		}

		query << "update network_map set prog_update_status=1 where c_id=" << _id ;

		dump_query(query.str());

		query.execute();

		trans.commit();
		
		ret = true;
		
		log(log_event::controller(_id,log_event::et_state_change,"В контроллер начала загружатся новая программа."));
	}
	catch(const std::exception &_error)
	{
	    ret = false;
	}

	if (connection)
		pool_->release(connection);

	return ret;
}

bool sqlhandle::end_program_update(const con_state::controller_id _id, bool _success)
{
	mysqlpp::Connection *connection = NULL;

	bool ret = true;

	try
	{
		connection = pool_->safe_grab();
		
		mysqlpp::Transaction trans(*connection);

		mysqlpp::Query query(connection);
		
		if (_success)
		{
		  query << "update network_map set prog_update_status=3, prev_enc_key=enc_key, enc_key=next_enc_key, prog_update_time=NOW() where c_id=" << _id;
		}
		else
		{
		  query << "update network_map set prog_update_status=4 where c_id=" << _id ;
		}  

		dump_query(query.str());

		query.execute();

		trans.commit();
		
		if (_success)
		{
		  log(log_event::controller(_id,log_event::et_state_change,"Программа в контроллере [успешно] обновлена."));
		}
		else
		{
		  log(log_event::controller(_id,log_event::et_state_change,"Произошла ошибка при обновлении программы."));
		}  
		
		ret = true;

	}
	catch(const std::exception &_error)
	{
		g_log.msg(MSG_NORMAL, str(boost::format("Start program error: %s") % _error.what()));
	    ret = false;
	}

	if (connection)
		pool_->release(connection);

	return ret;
}

void sqlhandle::log(const log_event & _event)
{
	mysqlpp::Connection *connection = NULL;

	try
	{
		connection = pool_->safe_grab();

		mysqlpp::Transaction trans(*connection);

		mysqlpp::Query query(connection);

		mysqlpp::Null<unsigned int> fault(mysqlpp::null);
		
		if (_event.fault_ != 0)
		{
		  fault = _event.fault_;
		}

	    query << "insert into log (log_date, log_type, c_id, imp, log_msg, user, log_fault) VALUES (NOW(), "
	    
	    			<< _event.le_ << ","<<_event.cont_ << "," << _event.imp_<< "," << mysqlpp::quote <<_event.message_<< "," << mysqlpp::quote << _event.user_ << ","
	    			
	    			<< fault << ")";
	    			
	    			
	    			
		dump_query(query.str());

		query.execute();

		trans.commit();

	}
	catch(const std::exception &_error)
	{
	}

	if (connection)
		pool_->release(connection);
}

void sqlhandle::log(log_event::log_event_list_t _list)
{
	mysqlpp::Connection *connection = NULL;

	try
	{
		connection = pool_->safe_grab();

		mysqlpp::Transaction trans(*connection);

		mysqlpp::Query query(connection);
		
		query << "insert into log (log_date, log_type, c_id, imp, log_msg, user, log_fault) VALUES ";
		
		
		for (log_event::log_event_list_t::iterator it = _list.begin(); it != _list.end(); it++)
		{
		
		  if (it != _list.begin())
		  {
			query << ",";
		  }
		  
		  mysqlpp::Null<unsigned int> fault(mysqlpp::null);
		
		  if (it->fault_ != 0)
		  {
			  fault = it->fault_;
		  }
		
		  query << "(NOW(), " << it->le_ << "," << it->cont_ << "," << it->imp_<< "," << mysqlpp::quote << it->message_<< ",'"
				  << it->user_ << "'," << fault << ")";
		}

		dump_query(query.str());

		query.execute();

		trans.commit();

	}
	catch(const std::exception &_error)
	{
	}

	if (connection)
		pool_->release(connection);
}

log_event::log_event(log_event::log_event_type_t _le,
						con_state::controller_id _cont,
						log_event::log_importance_t _imp,
						const std::string &_message,
						const std::string &_user,
						unsigned int _fault):
		le_(_le),
		cont_(_cont),
		imp_(_imp),
		message_(_message),
		user_(_user),
		fault_(_fault)
{
}

log_event log_event::debug(log_importance_t _imp, const std::string &_message, const std::string &_user)
{
	return log_event(le_debug, 0, _imp, _message, _user, 0);
}

log_event log_event::controller(con_state::controller_id _cont, log_importance_t _imp, const std::string &_message, const std::string &_user)
{
	return log_event(le_controller, _cont, _imp, _message, _user, 0);
}

log_event log_event::message(log_importance_t _imp, const std::string &_message, const std::string &_user)
{
	return log_event(le_main, 0, _imp, _message, _user, 0);
}

log_event log_event::controller(con_state::controller_id _cont,
		log_importance_t _imp, const std::string& _message)
{
	return log_event(le_controller, _cont, _imp, _message, "", 0);
}

log_event log_event::controller(con_state::controller_id _cont,
		log_importance_t _imp, const std::string& _message, unsigned int _fault)
{
	return log_event(le_controller, _cont, _imp, _message, "", _fault);

}
